package rasterprimate;

import java.util.ArrayList;
import java.util.Iterator;


import repast.simphony.context.Context;
import repast.simphony.engine.environment.RunEnvironment;
import repast.simphony.engine.schedule.ScheduledMethod;
import repast.simphony.parameter.Parameters;
import repast.simphony.random.RandomHelper;
import repast.simphony.space.continuous.ContinuousSpace;
import repast.simphony.space.gis.Geography;
import repast.simphony.space.grid.Grid;
import repast.simphony.space.grid.GridPoint;
import repast.simphony.util.ContextUtils;
import utils.ForagingUtils;
import utils.GroupUtils;
import utils.MoveUtils;
import utils.SimUtils;

import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.Envelope;
import com.vividsolutions.jts.geom.Geometry;

public class PrimateTopo {

	//********************input parameters********************//
	private Parameters p;

	//safety
	private int minSafety ; //the minimum number of neighbors that can be considered to be safe
	private int maxSafety ; //the maximum number of neighbors that can be considered

	private int safeNeighSizeUP= Constants.safeDelta;	//factor used to modify safeneighSize when feeding targets are met
	private int safeNeighSizeDown=Constants.safeDelta;	//factor used to modify safeneighSize when feeding targets are not met

	//energy balance
	private double targetEnergyPerDay = 100;
	private double energyLossPerStep = targetEnergyPerDay/Constants.stepsPerDay;  
	private double energyGainPerFeeding = targetEnergyPerDay / (Constants.stepsPerDay*Constants.feedingTime);

	//memory
	private int memSize = Constants.memRetention;

	//***********variables used for monitoring and running the simulation***********//

	//System level variables
	private Context context;
	private Geography<Object> geog;
	private Grid<Object> grid;
	private ContinuousSpace<Object> space;
	private Geometry geom;
	private long day;
	private int stepsPerDay=Constants.stepsPerDay;

	//PrimateTopo characteristics
	public int idNumb;
	private String name;
	private String sex;
	private int myGroup;
	private ArrayList<PrimateTopo> groupMates;
	private int safeNeighSize;
	private Coordinate coordinate;

	//resource decision making
	private ArrayList<FoodSourceMemTopo> knownFoodSource=null;
	private boolean enoughFood,haveTargetFoodSource;
	private double energy;
	private int numbEatingEvents;
	private int eatingEventsPerDay;
	private Resource myPatch=null;
	private FoodSourceMemTopo lastMem = null;

	//Movement behaviour variables
	private double distanceDayAvg;
	private int numbMoves;
	public long tagID=0; 
	private MarkerPoint previousPoint;


	public PrimateTopo(String maleFemale, Environment environment, int groupId, int groupSize,int primateNumber,Coordinate coord){

		//get/set model Parameters
		p = RunEnvironment.getInstance().getParameters();
		minSafety = Constants.minSafety;
		maxSafety = Constants.maxSafety;

		sex = maleFemale;
		day = 0;
		myGroup = groupId;
		safeNeighSize = groupSize-1;
		idNumb=primateNumber;		
		name=maleFemale+idNumb+"_Group"+groupId;

		this.setCoord(coord);
		this.setEnergy(RandomHelper.createNormal(100, 10).nextDouble());//energy to start with

		//create array to store food Source memories
		knownFoodSource= new ArrayList<FoodSourceMemTopo>();

		//set enough food to false initially
		setEnoughFood(false);

		//set inital maker point
		previousPoint = new MarkerPoint(geog,coord,0,0,0);
		
	}

	/************************************  Initialise memory  *********************************/
	
	@ScheduledMethod(start=1, priority=0)
	public void setUpMem(){
	//build initial memory of groups 
	buildInitialMemory();
	}

	/************************************  Behaviour SECTION  *********************************/
	
	@ScheduledMethod(start=0, interval = 1,priority=4)
	public void setGeog(){
		//update where the agent is on the geography and grid landscapes
		context = ContextUtils.getContext(this);
		geog = (Geography)context.getProjection("geog");
		grid = (Grid)context.getProjection("grid");
		geom = geog.getGeometry(this);
		coordinate = geom.getCoordinates()[0];
		GridPoint point = grid.getLocation(this);
		grid.moveTo(this,point.getX(),point.getY());

		//define who is in my group
		groupMates = GroupUtils.getPrimatesNearMe(this);

		//identify which patch i am in
		myPatch = SimUtils.findMyCurrentResource(this, geog);

	}

	@ScheduledMethod(start=2, interval = 1,priority=3,shuffle=true)
	public void step(){

		if (GroupUtils.checkIfSafe(this, this.getCoord())==false | myPatch==null){ // i'm not safe... 
			if(Constants.leaderBased==false){
				MoveUtils.moveToNearestGroup(this);
			}else{
				MoveUtils.moveTowardsLeader(this);
			}

		}else{ // i'm safe... 

			//Second: am i hungry???
			if(haveEnoughFood()==false){ 

				//look for food, compare visual + memory. If it is too far a intermediate site towards my goal is chosen
				Resource bestFoodSite = ForagingUtils.chooseFeedingSite(this);

				//i know were to find food
				if (bestFoodSite!=null){

					if(myPatch.getCoord()==bestFoodSite.getCoord() ){  // i am already at my chosen food site... agent eats form site
						
					}else{  // i'm not at my chosen food site... 
						MoveUtils.moveTo(this, bestFoodSite.getCoord());
					}

				//No food site chosen... 
				}else{

				}

				//I am not hungry...
			}else{
				//rest
			}
		}

		if (haveEnoughFood()==false){
			eat(SimUtils.findMyCurrentResource(this, geog));
		}else{
			safeNeighSizeUP();
		}
		

		//********************* update important variable *************************//

		//age my memory
		for(FoodSourceMemTopo fM: knownFoodSource){
			fM.step();
		}

		//energy lost per step
		this.setEnergy(this.getEnergy()-energyLossPerStep);

		//update daily variables
		if (RunEnvironment.getInstance().getCurrentSchedule().getTickCount()%stepsPerDay==0){
			setAddDay();
			calculatedDayDistanceAvg();
		}

	}



	/************************************  METHODS SECTION  *********************************/


	/************************************  Memory building **********************************/
	
	private void buildInitialMemory(){
		
		Iterator<Resource> resList = this.context.getObjects(Resource.class).iterator();
		ArrayList<Resource> resArray = new ArrayList<Resource>();
		ArrayList<Resource> resArrayRem = new ArrayList<Resource>();
		
		while (resList.hasNext()){
		resArray.add(resList.next());
		}
		
		//add largest food sites to memory, until memory size is reached
		while (knownFoodSource.size()<memSize){
			double largestFoodSiteValue = 0;
			Resource largestFoodSite = null;
			
			//find largest food site
			for (Resource r : resArray){
					if (largestFoodSiteValue<r.getmaxResLevel() && resArrayRem.contains(r)==false){
						largestFoodSite = r;
						largestFoodSiteValue = r.getmaxResLevel();
					}
			}
			
			knownFoodSource.add(new FoodSourceMemTopo(largestFoodSite));
			largestFoodSite.setRemembered(largestFoodSite.getRemembered()+1); // used to visuallize the memory distribution
			resArrayRem.add(largestFoodSite);
			
		}
		for (FoodSourceMemTopo fmTopo: knownFoodSource){
			fmTopo.setNearBySites(geog, knownFoodSource);
		}
	}

	/************************************* Group Behaviour ***********************************/

	//increases safe neighbour size
	private void safeNeighSizeUP(){
		if (safeNeighSize+safeNeighSizeUP<maxSafety){
			safeNeighSize=safeNeighSize+safeNeighSizeUP;
		}else{
			safeNeighSize= maxSafety;
			if (safeNeighSize <minSafety)safeNeighSize=minSafety; 
		}
	}

	//decreases safe neighbour size
	private void safeNeighSizeDown(){
		safeNeighSize = safeNeighSize - safeNeighSizeDown;
		if (safeNeighSize<minSafety)safeNeighSize=minSafety;
	}



	/************************************* Feeding Behaviour ***********************************/

	//is my energy level above the target level?
	private boolean haveEnoughFood(){
		boolean retVal=true;
		setEnoughFood(true);
		
		if (this.getEnergy()<=targetEnergyPerDay){
			retVal=false;
			setEnoughFood(false);
		}
		
		return retVal;
	}

	//eat from given resource (increasing energy of primate and reducing food at resource)
	private  boolean eat(Resource r){

		boolean retval = true;
		if (r != null){
			double energyIntake = r.eatMe(energyGainPerFeeding);
			this.setEnergy(this.getEnergy()+energyIntake);

			//if sufficient food is eaten record this as an eating event
			if (energyIntake==energyGainPerFeeding){
				this.addEatingEvent();
			}else{
				//return not enough food... safe neigh size down
				retval = false;
				safeNeighSizeDown();
			}

			//remember reslevel of food site if it is a significant spot
			rememberFoodSite(r);

		} else if (r == null){
			System.out.println("Resource not found!! "+ this.idNumb);
		}
		return retval;
	}

	//updated memory before leaving the patch
	private void rememberFoodSite(Resource r){

		//check to see if i already remember this site, if yes update memory
		boolean retval =false;
		for(FoodSourceMemTopo fM: knownFoodSource){
			if(r.getCoord()==fM.getCoord()){				
					fM.setFoodRemembered(r.getresLevel());
					lastMem = fM; //set this as my last familar site
				retval = true;
			}								
		}

		if(retval==false){ 
			if (memSize>knownFoodSource.size()){

				//if i don't remember it, add it to memory
				FoodSourceMemTopo newTopoMem = new FoodSourceMemTopo(r);
				knownFoodSource.add(newTopoMem);
				lastMem = newTopoMem;
				r.setRemembered(r.getRemembered()+1); // used to visuallize the memory distribution

			}else if (memSize==knownFoodSource.size() & memSize!=0){

				//find smallest resource remembered
				FoodSourceMemTopo fMin = knownFoodSource.get(0);
				for (FoodSourceMemTopo f : knownFoodSource){
					if (fMin.getFoodCapacity()>f.getFoodCapacity())fMin=f;
				}

				//check to see if this resource is larger than the smallest one in memory
				
				//specific eating events remembered
				if (fMin.getFoodCapacity()<r.getmaxResLevel()){
					knownFoodSource.remove(fMin);
					fMin.getResource().setRemembered(fMin.getResource().getRemembered()-1);
					FoodSourceMemTopo newMem = new FoodSourceMemTopo(r);
					newMem.getResource().setRemembered(newMem.getResource().getRemembered()+1);
					knownFoodSource.add(newMem);
				}

			}else{
				System.out.println("there is something wrong with the mem algo: # mems = " + knownFoodSource.size());
			}
		}
	}


	//keeps track of the number of times the individual moved
	private void calculatedDayDistanceAvg(){
		distanceDayAvg=getNumbMoves();
		setNumbMoves(0);
	}



	/********************************get and set functions************************************/

	
	private double getAngle(Coordinate first, Coordinate second){
		double angle=0;
		
		//calculate angle at which to move
		double adjasent = (double)(second.x-first.x);
		double opposite = (double)(second.y-first.y);

		angle = Math.atan(Math.abs(opposite/adjasent));
		if (adjasent<0) angle=(Math.PI)-angle;
		if (opposite<0) angle=2*(Math.PI)-angle;
		 
		return angle;
	}

	public void setCoord(Coordinate coord){
		coordinate = coord;
	}
	public Coordinate getCoord(){
		return coordinate;
	}
	public String getName(){
		return name;
	}
	public double getEnergy(){
		return energy;
	}
	public void setEnergy(double E){
		energy = E;
	}
	public void setName(String test){
		name = test;
	}
	public String getSex(){
		return sex;
	}
	public boolean getEnoughFood(){
		return enoughFood;
	}
	public void setEnoughFood(boolean t){
		enoughFood=t;
	}
	public void setHaveTargetFoodSource(boolean t){
		haveTargetFoodSource = t;
	}
	public boolean getHaveTargetFoodSource(){
		return haveTargetFoodSource;
	}
	private Context getContext(){
		return context;
	}
	public int getMyGroupId(){
		return myGroup;
	}
	public long getDay(){
		return day;
	}
	private void setAddDay(){
		day++;
	}
	public double getDayDistanceAvg(){
		return distanceDayAvg;
	}
	public void addNumbMoves(){
		numbMoves++;
	}
	private int getNumbMoves(){
		return numbMoves;
	}
	private void setNumbMoves(int i){
		numbMoves = i;
	}
	private void addEatingEvent(){
		numbEatingEvents++;
	}
	private int getEatingEvents(){
		return numbEatingEvents;
	}
	private void setEatingEvents(int i){
		numbEatingEvents = i;	
	}
	public void setEatingEventsPerDay(int i){
		eatingEventsPerDay=i;
	}
	public int getEatingEventsPerDay(){
		return eatingEventsPerDay;
	}
	public int getSafeNeighSize(){
		return safeNeighSize;
	}
	public Geography getGeog(){
		return geog;
	}
	public Grid getGrid(){
		return grid;
	}
	public ArrayList<FoodSourceMemTopo> getMemory(){
		return knownFoodSource;
	}
	public ArrayList<PrimateTopo> getGroupMates(){
		return groupMates;
	}
	public void updateMyPatch(Resource n){
		myPatch = n;
	}
	public Resource getMyPatch(){
		return myPatch;
	}
	public FoodSourceMemTopo getLastRememberedSite(){
		return lastMem;
	}
	public void setLastRememberedSite(FoodSourceMemTopo s){
		lastMem = s;
	}
}
